package com.jlf.core.engine.meta;

import com.jlf.core.engine.domain.SchemaDomainDO;
import com.jlf.core.engine.domain.SchemaFieldDO;
import com.jlf.core.engine.domain.SchemaIndexDO;
import com.jlf.core.engine.entity.MetaColumn;
import com.jlf.core.engine.entity.MetaIndex;
import com.jlf.core.engine.entity.MetaTable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据库的元数据临时缓存
 *
 * @author wujr
 * 2023/7/14
 * @version 1.0
 * 【变更历史】
 * [wujr 2023/7/14 1.0 新增]
 */
@Slf4j
@RequiredArgsConstructor
public class MetaRepository {
    private static final String tableNamePattern = "t\\_%";
    private final DataSource dataSource;
    @Getter
    private String dbProduct;
    @Getter
    private String dbVersion;
    private String catalog;
    private String schemaPattern;

    public boolean isPostgresql(){
        return "postgresql".equalsIgnoreCase(dbProduct);
    }
    public boolean isMySql(){
        return "mysql".equalsIgnoreCase(dbProduct);
    }
    public boolean isOracle(){
        return "oracle".equalsIgnoreCase(dbProduct);
    }

    public boolean difference(MetaTable metaTable,
                              SchemaDomainDO schemaDomain,
                              List<SchemaFieldDO> schemaFields,
                              List<SchemaIndexDO> schemaIndexes,
                              List<SchemaFieldDO> newFields, List<SchemaFieldDO> modFields, List<SchemaFieldDO> delFields,
                              List<SchemaIndexDO> newIndexes, List<SchemaIndexDO> modIndexes, List<SchemaIndexDO> delIndexes){
        return metaTable.difference(schemaFields, schemaIndexes, newFields, modFields, delFields, newIndexes, modIndexes, delIndexes);
    }
    public MetaTable loadTable(String tableName){
        try {
            Map<String, MetaTable> mapTables = load(tableName);
            return mapTables.get(tableName);
        }catch(Exception e){
            return null;
        }
    }
    public Map<String, MetaTable> load(String tableName) throws Exception{
        Connection connection = dataSource.getConnection();
        DatabaseMetaData metaData = connection.getMetaData();
        dbProduct = metaData.getDatabaseProductName();
        dbVersion = metaData.getDatabaseProductVersion();
        if (isPostgresql()){
            this.catalog = connection.getCatalog();
            this.schemaPattern = "public";
        }else if (isMySql()){
            this.catalog = null;
            this.schemaPattern = connection.getCatalog();
        }else if (isOracle()){
            this.catalog = null;
            this.schemaPattern = metaData.getUserName();
        }
        // 加载数据表
        Map<String, MetaTable> mapTables = loadTable(metaData, tableName);
        // 获取这些表格的字段
        loadColumns(metaData, mapTables, tableName);
        // 获取这些表格的索引
        for (Map.Entry<String, MetaTable> entry : mapTables.entrySet()){
            MetaTable metaTable = entry.getValue();
            tableName = entry.getKey();
            loadIndex(metaData, tableName, metaTable);
        }
        log.trace("加载数据库的元数据完毕！");
        return mapTables;
    }
    private Map<String, MetaTable> loadTable(DatabaseMetaData metaData, String tableName) throws Exception{
        String tablePattern = StringUtils.isBlank(tableName) ? tableNamePattern : tableName;
        ResultSet rsTable = metaData.getTables(this.catalog, this.schemaPattern, tablePattern, new String[]{"TABLE", "VIEW"});
        Map<String, MetaTable> mapTables = new HashMap<>();
        while (rsTable.next()){
            String catalogName = rsTable.getString("TABLE_CAT");
            String schemaName = rsTable.getString("TABLE_SCHEM");
            tableName = rsTable.getString("TABLE_NAME");
            String tableType = rsTable.getString("TABLE_TYPE");
            String tableRemark = rsTable.getString("REMARKS");
            MetaTable r = new MetaTable();
            r.setCatalog(catalogName);
            r.setSchema(schemaName);
            r.setName(tableName);
            r.setType(tableType);
            r.setRemark(tableRemark);
            mapTables.put(r.getName(), r);
        }
        return mapTables;
    }
    private void loadColumns(DatabaseMetaData metaData, Map<String, MetaTable> mapTables, String tableName) throws Exception{
        String tablePattern = StringUtils.isBlank(tableName) ? tableNamePattern : tableName;
        ResultSet rs = metaData.getColumns(this.catalog, this.schemaPattern, tablePattern, null);
        while (rs.next()){
            tableName = rs.getString("TABLE_NAME");
            String columnName = rs.getString("COLUMN_NAME");
            int dataType = rs.getInt("DATA_TYPE");
            int columnSize = rs.getInt("COLUMN_SIZE");
            int nullable = rs.getInt("NULLABLE");
            String remark = rs.getString("REMARKS");
            String decimalDigit = rs.getString("DECIMAL_DIGITS");
            String defaultValue = rs.getString("COLUMN_DEF");
            int radix = rs.getInt("NUM_PREC_RADIX");
            String isAutoIncrement = rs.getString("IS_AUTOINCREMENT");

            MetaColumn metaColumn = new MetaColumn();
            metaColumn.setName(columnName);
            metaColumn.setType(dataType);
            metaColumn.setLength(columnSize);
            if (DatabaseMetaData.columnNoNulls == nullable){
                metaColumn.setNullable(Boolean.FALSE);
            }else if (DatabaseMetaData.columnNullable == nullable){
                metaColumn.setNullable(Boolean.TRUE);
            }
            metaColumn.setRemark(remark);
            if (!StringUtils.isBlank(decimalDigit)){
                metaColumn.setDecimalDigit(Integer.valueOf(decimalDigit));
            }
            metaColumn.setDefaultValue(defaultValue);
            metaColumn.setRadix(radix);
            if ("yes".equalsIgnoreCase(isAutoIncrement)){
                metaColumn.setAutoIncrement(Boolean.TRUE);
            }else if ("no".equalsIgnoreCase(isAutoIncrement)){
                metaColumn.setAutoIncrement(Boolean.FALSE);
            }

            MetaTable metaTable = mapTables.get(tableName);
            metaTable.addColumn(metaColumn);
        }
    }
    private void loadIndex(DatabaseMetaData metaData, String tableName, MetaTable metaTable) throws Exception{
        ResultSet rs = metaData.getIndexInfo(this.catalog, this.schemaPattern, tableName, false, true);
        Map<String, MetaIndex> mapIndex = new HashMap<>();
        while (rs.next()){
            String indexName = rs.getString("INDEX_NAME");
            MetaIndex index = mapIndex.computeIfAbsent(indexName, k -> {
                MetaIndex r = new MetaIndex();
                r.setName(k);
                try {
                    boolean unique = rs.getBoolean("NON_UNIQUE");
                    Short type = rs.getShort("TYPE");
                    String qualifier = rs.getString("INDEX_QUALIFIER");
                    Short position = rs.getShort("ORDINAL_POSITION");
                    String ascOrDesc = rs.getString("ASC_OR_DESC");
                    int cardinality = rs.getInt("CARDINALITY");
                    int pages = rs.getInt("PAGES");
                    String filterCondition = rs.getString("FILTER_CONDITION");
                    r.setUnique(!unique);
                    r.setType(type);
                    r.setQualifier(qualifier);
                    r.setPosition(position);
                    r.setAscOrDesc(ascOrDesc);
                    r.setCardinality(cardinality);
                    r.setPages(pages);
                    r.setFilterCondition(filterCondition);
                }catch(Exception e){
                    log.info("读取索引信息异常：", e);
                }
                metaTable.addIndex(r);
                return r;
            });
            String columnName = rs.getString("COLUMN_NAME");

            index.addColumn(columnName);
        }
    }
}
